<?php
// +-------------------------------------------------------------------------+
// | PHP Version 5                                                           |
// +-------------------------------------------------------------------------+
// | ImaEngine - web development framework                                   |
// | Copyright (c) 2006-2007 Sardar Yumatov <ja.doma@gmail.com>              |
// +-------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify it |
// | under the terms of the GNU Lesser General Public License as published   |
// | by the Free Software Foundation; either version 2.1 of the License, or  |
// | (at your option) any later version.                                     |
// |                                                                         |
// | This library is distributed in the hope that it will be useful, but     |
// | WITHOUT ANY WARRANTY; without even the implied warranty of              |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    |
// | See the GNU Lesser General Public License for more details.             |
// |                                                                         |
// | You should have received a copy of the GNU Lesser General Public        |
// | License along with this library; if not, write to the Free Software     |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
// +-------------------------------------------------------------------------+

/**
* Locale detection script.
* This script collects locale info from user request. Based on this info other modules may generate
* localized content.
*
* By definition there are two terms used in loclization:
*   - language -- ISO-639-1 2 letters lowercase language code (ru)
*   - locale -- full locale name in format [a-z]{2}_[A-Z]{2} (en_US)
*
* All locales define a subset of dialects within a language. No charset info is used because
* the engine always works in unicode (UTF-8 in PHP5).
*
* Script parses "locale string", which has following syntax:
*   . := {entry} (',' {entry})*
*   entry := {locale} {priority}?
*   locale := {lang}([\-_]{lang})?
*   lang := [a-zA-Z]{2}
*   priority := ';q=' (0\.[0-9]+) | ([0-1])
*
* Example of locale-string: ru,en-us;q=0.8,en;q=0.5,nl;q=0.3
*
* In other words the syntax is identical to Accept-Language HTTP header. One or more locales/languages
* may be listed with different priority. If priority is not set, then it used as priority 1 (highest priority).
* All locales will be sorted by priorty and the order they where read. Locales not supported by the
* site will be filtered out, then topmost from the list will be used as current locale.
*
* Locale string will be build by concatenation from (ordered by priority):
*   - $_GET['tlang'], $_POST['tlang'] -- temporary interface language (current request only)
*   - $_GET['lang'], $_POST['lang'] -- interface language, will be set to cookie if such feature is enabled
*   - $_COOKIE['lang']? -- persistent settings from cookie if such feature is enabled
*   - $_SERVER['HTTP_ACCEPT_LANGUAGE']? -- Accept-Language header field from HTTP request, sent by browser, used if enabled from configuration
*   - config [locale]host.{current host} -- default locale/language for current host/domain
*   - config [locale]default-locale -- default (fallback) locale/language
*
* <b>Note:</b> if a site provides different content for localized versions, then returning content by HTTP_ACCEPT_LANGUAGE
* is probably bad idea. That is why this must be explicitly enabled from configuration <b>[locale]enable-http-access-language</b>
*
* The difference between lang and tlang is that descision made by lang will be send back
* as set-cookie, so the next request will not need lang again. The tlang changes locale/language only
* for current request, no set-cookie info will be send back to the user. However if config <b>[locale]enable-cookie-set</b>
* is not set, then lang and tlang are equivalent, except that tlang has higher priority.
* The cookie can be deleted by providing <b>$_REQUEST['reset_lang']</b> parameter.
*
* <b>Note:</b> storing lang parameter in cookie is probably bad idea if your site has different content for different languages.
* It is possible that the same URL will return different content on different computers because of previously stored cookie. That
* is why this feature is disabled by default.
*
*  All collected info will be available in (all lists sorted by priority):
*   - $locale['user-locales'] -- locales requested by the user and defaults from configuraiton [en_US, en_GB, ru_RU, nl_NL]
*   - $locale['user-languages'] -- languages requested by the user and defaults from configuration [en, ru, nl]
*   - $locale['system-locales'] -- locales supported by the engine (interface), from <tt>[locale]list:supported-locales</tt> config
*   - $locale['system-languages'] -- languages supported by the engine (interface)
*   - $locale['agent-locales'] -- locales provided by HTTP_ACCEPT_LANGUAGE header only, may be empty list
*   - $locale['agent-languages'] -- languages provided by HTTP_ACCEPT_LANGUAGE header only, may be empty list
*   - $locale['input-locales'] -- locales provided by $_REQUEST['clang/tlang/lang'] only in that priority order, may be empty list
*   - $locale['input-languages'] -- languages provided by $_REQUEST['clang/tlang/lang'] only in that priority order, may be empty list
*
*   - $locale['locales'] -- intersection of user-locales and system-locales
*   - $locale['languages'] -- intersection of user-languages and system-languages
*   - $locale['locale'] -- current locale, topmost from 'locales' (en_US)
*   - $locale['language'] -- current language, topmost from 'languages' (en)
*
*   - $locale['set-locale'] -- returned value from setlocale(LC_ALL, $locale['locale'].'.utf8', $locale['locale'], $locale['language']);
*	- $locale['cookie-set'] -- will be true if cookies are enabled and this request has changed the cookie (lang was provided)
*	- $locale['cookie-used'] -- will be true if cookies are enabled and non empty $_COOKIE['lang'] was provided
* 	- $locale['cookie-preferred'] -- will be true if cookies are enabled and info from cookie has the highest priority.
*
* <b>Note:</b> the 'cookie-preffered' doesn't indicate that current locale is chosen according to the cookie, this will happen only
* if locale provided by the cookie does exist in 'system-locales'. Otherwise a lower by priority, but wel existing locale will
* be chosen instead.
*
* Warning: when porting code to another platform <b>ensure</b> the charset is still '.utf8', because different
* fucking *nix systems may require '.UTF8', '.utf-8' and alike! Also note that on windows system the return value
* will be something like 'Russian_Russia.1251' on input of 'ru_RU.utf8'.
*
* All lists will be ordered by priority. The list of languages will always be smaller or equal in size
* to the list of locales.
* System locales and languages are defined in config <tt>[locale]list:supported-locales</tt>.
* List of requested by the user locales/languages will always contain some locales since some of them are taken from
* configuration as fallback. Thus the intersections will have at least one element.
*
* Sometimes the content may be delivered in diferent language than interface. Therefore are content-*
* sets defined, that include (in priority order):
*  - locales/languages from $_GET['clang'] + $_POST['clang'] locale-string
*  - $locale['user-*'] -- probably non supported locales/languages from input, cookie and Accep-Language header
*  - $locale['locales/languages'] -- supported by the system requested locales/languages
*
* Module may ignore content-* and use $locale['locale/language'] if there is no reson to deliver content in special
* language. Following variables will be defined, all lists sorted by priority:
*   - $locale['content-locales'] -- content locales requested by the user (see above)
*   - $locale['content-langauges'] -- content languages requested by the user (see above)
*   - $locale['content-locale'] -- top most entry from content-locales
*   - $locale['content-language'] -- top most entry from content-languages
*
* Please note that content-* will probably have non supported by the system locales first. Thus you will
* need to iterate from first element to find frst matched resource. The clang variable will not be set to
* cookie, thus should be send with each request.
*
* Note: lang, tlang and clang may be arrays, the values will be simply concatenaded by ','.
*
* Configuration used:
*   - [locale]boolean:enable-http-access-language - add Access-Language header info to user-locales/languages
*   - [locale]boolean:enable-cookie-set - use cookie to store chosen language (lang)
*   - [locale]default-locale - string or list - default locale (fallback)
*   - [locale]list:supported-locales - list of strings - installed list of locales (interface)
*   - [locale]host.(*host) - string or list - smapping of host to list of preffered locales (host.mydomain.ru => ru;q=0.1)
*
* Changelog:
*  - v1.1
*    HTTP_ACCEPT_LANGUAGE is disabled by default, can be enabled from configuration.
*    cookie:lang is disabled by default, can be enabled from configuration
*    agent-* added, which contains locales specified by HTTP_ACCEPT_LANGUAGE only
*    input-* added, which contains locales specified by $_REQUEST['lang/tlang/clang'] only
*    reset_lang REQUEST parameter added, which disables lang cookie
*    Some bug fixes.
*
*
* @author Sardar Yumatov <ja.doma@gmail.com>
* @version v1.1
* @copyright Copyright &copy; 2006-2007 Sardar Yumatov
* @package boot
*
* @PHP5only
* @export $locale
* @config define UTF8_LOCALE_NAME
*/


if(!defined('PROCESS')) {
	header('HTTP/1.1 404 Not Found');
	die('NotFound');
}


//used for $environment['host']
if(!defined('PROCESS_ENVIRONMENT')) require_once('boot/environment.php');


require_once('lib/Configuration.php');
require_once('lib/L10N/Localization.php');


/**
 * The codepage name of UTF-8 on host platform.
 * Because most of *nix have different names here, ranging from UTF-8 to utf8.
 * @var string
 */
define('UTF8_LOCALE_NAME', '.utf8');


//$locale variable is available
define('PROCESS_LOCALE', true);


//The collected info, rationale of this script
$locale = array();

//collect all locale-string's by concatenation according to priority
//$_REQUEST may not be used for maximum flexibility
$loc = $inp = (isset($_GET['tlang'])? (is_array($_GET['tlang'])? implode(',', $_GET['tlang']): strval($_GET['tlang'])): '').','
	  .(isset($_POST['tlang'])? (is_array($_POST['tlang'])? implode(',', $_POST['tlang']): strval($_POST['tlang'])): '').','
	  .(isset($_GET['lang'])? (is_array($_GET['lang'])? implode(',', $_GET['lang']): strval($_GET['lang'])): '').','
	  .(isset($_POST['lang'])? (is_array($_POST['lang'])? implode(',', $_POST['lang']): strval($_POST['lang'])): '');

//use cookie
if(Configuration::get('[locale]boolean:enable-cookie-set', false)) {
	$locale['cookie-set'] = false;
	$locale['cookie-used'] = false;
	$locale['cookie-preferred'] = false;

	if(!headers_sent()) {
		if(isset($_REQUEST['reset_lang'])) setcookie('lang', '', time() - 30); //disable lang cookie
		elseif(isset($_GET['lang']) || isset($_POST['lang'])) { //enable/refresh lang cookie
			setcookie('lang', $_REQUEST['lang'], time() + 60*60*24*30); //30 days; [XXX: are we sure the variables_order is 'GPC'? ;-) ]
			$locale['cookie-set'] = true;
		}
	} else trigger_error("Can't send cookie info because headers are already sent! Ensure no output in boot scripts or use buffering!", E_USER_WARNING);

 	if(!isset($_REQUEST['reset_lang']) && isset($_COOKIE['lang'])) {
		$loc = ','.($cook = is_array($_COOKIE['lang'])? implode(',', $_COOKIE['lang']): strval($_COOKIE['lang']));
		$locale['cookie-used'] = trim($cook, " \t\r\n\0\x0B,") != '';
		$locale['cookie-preferred'] = (trim($inp, " \t\r\n\0\x0B,") == '' && $locale['cookie-used']); //lang and tlang were empty, but cookie not
		unset($cook);
 	}
}

//try to detect client locale settings
$loc .= (Configuration::get('[locale]boolean:enable-http-access-language', false) && isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))? ','.strval($_SERVER["HTTP_ACCEPT_LANGUAGE"]): '';

$domloc = Configuration::get("[locale]host.{$environment['host']}", '');
$domloc2 = Configuration::get('[locale]default-locale','');
$loc.= ','.(is_array($domloc)? implode(',', $domloc): strval($domloc)).','.(is_array($domloc2)? implode(',', $domloc2): strval($domloc2));
unset($domloc, $domloc2);

//total all input locales
$loc = Localization::parseLocaleString($loc); //all requested and default locales
$locale['user-locales'] = $loc['locales'];
$locale['user-languages'] = $loc['languages'];

//agent locales
$loc = isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])? Localization::parseLocaleString(strval($_SERVER["HTTP_ACCEPT_LANGUAGE"])): null;
$locale['agent-locales'] = is_null($loc)? array(): $loc['locales'];
$locale['agent-languages'] = is_null($loc)? array(): $loc['languages'];

//request input only locales
$loc = ($clng = (isset($_GET['clang'])? (is_array($_GET['clang'])? implode(',', $_GET['clang']): strval($_GET['clang'])): '').','
	  .(isset($_POST['clang'])? (is_array($_POST['clang'])? implode(',', $_POST['clang']): strval($_POST['clang'])): '')).','
	  .$inp;

$loc = Localization::parseLocaleString($loc); //all requested and default locales
$locale['input-locales'] = $loc['locales'];
$locale['input-languages'] = $loc['languages'];

unset($inp);


//merge installed with requested locales
$systemlocales=array(); $systemlanguages=array(); $mtch = null;
foreach(Configuration::getOption('[locale]list:supported-locales') as $entry) {
	try {
		$ret = Localization::parseLocale(trim($entry));
	} catch (InvalidArgumentException $e) {
		continue; //ignore wrong syntax
	}

	$systemlocales[$ret['locale']] = $ret['locale'];
	$systemlanguages[$ret['language']] = $ret['language'];
}
$locale['system-locales'] = $systemlocales;
$locale['system-languages'] = $systemlanguages;

//intersection, preserve priority order
$locale['languages'] = array_intersect_key($locale['user-languages'], $systemlanguages);
//BUGFIX: do not take intersect! allow locale as long as the language allows that
//$locale['locales'] = array_intersect_key($locale['user-locales'], $systemlocales);
foreach($locale['user-locales'] as $k=>$v) if(isset($systemlanguages[substr($k, 0, 2)])) $locale['locales'][$k] = $v;

unset($systemlanguages, $systemlocales, $lang, $languages, $locales, $ret);

if(sizeof($locale['locales']) == 0 || sizeof($locale['languages']) == 0)
	throw new InstallationException('No available locales to use. This means [locale] config module is not set correctly! Define at least default-locale, which must be in supported-locales set!');

reset($locale['locales']);
reset($locale['languages']);
$locale['locale'] = current($locale['locales']);
$locale['language'] = current($locale['languages']);


//==========- Content locale -=============
$loc = Localization::parseLocaleString($clng);
$locale['content-locales'] = $loc['locales'] + $locale['user-locales'] + $locale['locales'];
$locale['content-languages'] = $loc['languages'] + $locale['user-languages'] + $locale['languages'];

reset($locale['content-locales']);
reset($locale['content-languages']);
$locale['content-locale'] = current($locale['content-locales']);
$locale['content-language'] = current($locale['content-languages']);

//=======- Setup system locale -==========
//[XXX]Warning: because the PHP is fucking piece of shit, the locale may be changed for whole process
//instead of thread if running on threaded server (new Apache in threaded mode). This means the whole
//hosting will be affected! If apache is run in "fork" mode, then everything will work OK.
$locale['set-locale'] = setlocale(LC_ALL, $locale['locale'].UTF8_LOCALE_NAME, $locale['locale'], $locale['language']);

//clean up everything
unset($mtch, $entry, $loc, $clng, $v, $k);

?>